/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.

Licensed under the Oculus Master SDK License Version 1.0 (the "License"); you may not use
the Utilities SDK except in compliance with the License, which is provided at the time of installation
or download, or which otherwise accompanies this software in either electronic or hard copy form.

You may obtain a copy of the License at
https://developer.oculus.com/licenses/oculusmastersdk-1.0/

Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/


using UnityEngine;
using System.Collections;
using System.IO;

/// <summary>
/// Helper script for capture cubemap and save it into PNG or JPG file
/// </summary>

/// <description>
/// How it works:
/// 1) This script can be attached to a existing game object, you can also use prefab Assets\OVR\Prefabs\OVRCubemapCaptureProbe
///	There are 2 ways to trigger a capture if you attached this script to a game object.
///		* Automatic capturing: if [autoTriggerAfterLaunch] is true, a automatic capturing will be triggered after [autoTriggerDelay] seconds.
///		* Keyboard trigger: press key [triggeredByKey], a capturing will be triggered.
/// 2) If you like to trigger the screen capture in your code logic, just call static function [OVRCubemapCapture.TriggerCubemapCapture] with proper input arguments.
/// </description>

public class OVRCubemapCapture : MonoBehaviour
{
	/// <summary>
	/// Enable the automatic screenshot trigger, which will capture a cubemap after autoTriggerDelay (seconds)
	/// </summary>
	public bool autoTriggerAfterLaunch = true;
	public float autoTriggerDelay = 1.0f;
	private float autoTriggerElapse = 0.0f;

	/// <summary>
	/// Trigger cubemap screenshot if user pressed key triggeredByKey
	/// </summary>
	public KeyCode triggeredByKey = KeyCode.F8;

	/// <summary>
	/// The complete file path for saving the cubemap screenshot, including the filename and extension
	/// if pathName is blank, screenshots will be saved into %USERPROFILE%\Documents\OVR_ScreenShot360
	/// </summary>
	public string pathName;

	/// <summary>
	/// The cube face resolution
	/// </summary>
	public int cubemapSize = 2048;

	// Update is called once per frame
	void Update()
	{
		// Trigger after autoTriggerDelay
		if (autoTriggerAfterLaunch)
		{
			autoTriggerElapse += Time.deltaTime;
			if (autoTriggerElapse >= autoTriggerDelay)
			{
				autoTriggerAfterLaunch = false;
				TriggerCubemapCapture(transform.position, cubemapSize, pathName);
			}
		}

		// Trigger by press triggeredByKey
		if ( Input.GetKeyDown( triggeredByKey ) )
		{
			TriggerCubemapCapture(transform.position, cubemapSize, pathName);
		}
	}

	/// <summary>
	/// Generate unity cubemap at specific location and save into JPG/PNG
	/// </summary>
	/// <description>
	/// Default save folder: your app's persistentDataPath
	/// Default file name: using current time OVR_hh_mm_ss.png
	/// Note1: this will take a few seconds to finish
	/// Note2: if you only want to specify path not filename, please end [pathName] with "/"
	/// </description>

	public static void TriggerCubemapCapture(Vector3 capturePos, int cubemapSize = 2048, string pathName = null)
	{
		GameObject ownerObj = new GameObject("CubemapCamera", typeof(Camera));
		ownerObj.hideFlags = HideFlags.HideAndDontSave;
		ownerObj.transform.position = capturePos;
		ownerObj.transform.rotation = Quaternion.identity;
		Camera camComponent = ownerObj.GetComponent<Camera>();
		camComponent.farClipPlane = 10000.0f;
		camComponent.enabled = false;

		Cubemap cubemap = new Cubemap(cubemapSize, TextureFormat.RGB24, false);
		RenderIntoCubemap(camComponent, cubemap);
		SaveCubemapCapture(cubemap, pathName);
		DestroyImmediate(cubemap);
		DestroyImmediate(ownerObj);
	}


	public static void RenderIntoCubemap(Camera ownerCamera, Cubemap outCubemap)
	{
		int width = (int)outCubemap.width;
		int height = (int)outCubemap.height;

		CubemapFace[] faces = new CubemapFace[] { CubemapFace.PositiveX, CubemapFace.NegativeX, CubemapFace.PositiveY, CubemapFace.NegativeY, CubemapFace.PositiveZ, CubemapFace.NegativeZ };
		Vector3[] faceAngles = new Vector3[] { new Vector3(0.0f, 90.0f, 0.0f), new Vector3(0.0f, -90.0f, 0.0f), new Vector3(-90.0f, 0.0f, 0.0f), new Vector3(90.0f, 0.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 180.0f, 0.0f) };

		// Backup states
		RenderTexture backupRenderTex = RenderTexture.active;
		float backupFieldOfView = ownerCamera.fieldOfView;
		float backupAspect = ownerCamera.aspect;
		Quaternion backupRot = ownerCamera.transform.rotation;
		//RenderTexture backupRT = ownerCamera.targetTexture;

		// Enable 8X MSAA
		RenderTexture faceTexture = new RenderTexture(width, height, 24);
		faceTexture.antiAliasing = 8;
		faceTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
		faceTexture.hideFlags = HideFlags.HideAndDontSave;

		// For intermediate saving
		Texture2D swapTex = new Texture2D(width, height, TextureFormat.RGB24, false);
		swapTex.hideFlags = HideFlags.HideAndDontSave;

		// Capture 6 Directions
		ownerCamera.targetTexture = faceTexture;
		ownerCamera.fieldOfView = 90;
		ownerCamera.aspect = 1.0f;

		Color[] mirroredPixels = new Color[swapTex.height * swapTex.width];
		for (int i = 0; i < faces.Length; i++)
		{
			ownerCamera.transform.eulerAngles = faceAngles[i];
			ownerCamera.Render();
			RenderTexture.active = faceTexture;
			swapTex.ReadPixels(new Rect(0, 0, width, height), 0, 0);

			// Mirror vertically to meet the standard of unity cubemap
			Color[] OrignalPixels = swapTex.GetPixels();
			for (int y1 = 0; y1 < height; y1++)
			{
				for (int x1 = 0; x1 < width; x1++)
				{
					mirroredPixels[y1 * width + x1] = OrignalPixels[((height - 1 - y1) * width) + x1];
				}
			};
			outCubemap.SetPixels(mirroredPixels, faces[i]);
		}

		outCubemap.SmoothEdges();

		// Restore states
		RenderTexture.active = backupRenderTex;
		ownerCamera.fieldOfView = backupFieldOfView;
		ownerCamera.aspect = backupAspect;
		ownerCamera.transform.rotation = backupRot;
		ownerCamera.targetTexture = backupRenderTex;

		DestroyImmediate(swapTex);
		DestroyImmediate(faceTexture);

		}


	/// <summary>
	/// Save unity cubemap into NPOT 6x1 cubemap/texture atlas in the following format PX NX PY NY PZ NZ
	/// </summary>
	/// <description>
	/// Supported format: PNG/JPG
	/// Default file name: using current time OVR_hh_mm_ss.png
	/// </description>

	public static bool SaveCubemapCapture(Cubemap cubemap, string pathName = null)
	{
		string fileName;
		string dirName;
		int width = cubemap.width;
		int height = cubemap.height;
		int x = 0;
		int y = 0;
		bool saveToPNG = true;

		if (string.IsNullOrEmpty(pathName))
		{
			dirName = Application.persistentDataPath + "/OVR_ScreenShot360/";
			fileName = null;
		}
		else
		{
			dirName = Path.GetDirectoryName(pathName);
			fileName = Path.GetFileName(pathName);

			if (dirName[dirName.Length - 1] != '/' || dirName[dirName.Length - 1] != '\\')
				dirName += "/";
		}

		if (string.IsNullOrEmpty(fileName))
			fileName = "OVR_" + System.DateTime.Now.ToString("hh_mm_ss") + ".png";

		string extName = Path.GetExtension(fileName);
		if (extName == ".png")
		{
			saveToPNG = true;
		}
		else if (extName == ".jpg")
		{
			saveToPNG = false;
		}
		else
		{
            Debug.LogError("Unsupported file format" + extName);
			return false;
		}

		// Validate path
		try
		{
			System.IO.Directory.CreateDirectory(dirName);
		}
		catch (System.Exception e)
		{
            Debug.LogError("Failed to create path " + dirName + " since " + e.ToString());
			return false;
		}


		// Create the new texture
		Texture2D tex = new Texture2D(width * 6, height, TextureFormat.RGB24, false);
		if (tex == null)
		{
			Debug.LogError("[OVRScreenshotWizard] Failed creating the texture!");
			return false;
		}

		// Merge all the cubemap faces into the texture
		// Reference cubemap format: http://docs.unity3d.com/Manual/class-Cubemap.html
		CubemapFace[] faces = new CubemapFace[] { CubemapFace.PositiveX, CubemapFace.NegativeX, CubemapFace.PositiveY, CubemapFace.NegativeY, CubemapFace.PositiveZ, CubemapFace.NegativeZ };
		for (int i = 0; i < faces.Length; i++)
		{
			// get the pixels from the cubemap
			Color[] srcPixels = null;
			Color[] pixels = cubemap.GetPixels(faces[i]);
			// if desired, flip them as they are ordered left to right, bottom to top
			srcPixels = new Color[pixels.Length];
			for (int y1 = 0; y1 < height; y1++)
			{
				for (int x1 = 0; x1 < width; x1++)
				{
					srcPixels[y1 * width + x1] = pixels[((height - 1 - y1) * width) + x1];
				}
			}
			// Copy them to the dest texture
			tex.SetPixels(x, y, width, height, srcPixels);
			x += width;
		}

        try
        {
            // Encode the texture and save it to disk
            byte[] bytes = saveToPNG ? tex.EncodeToPNG() : tex.EncodeToJPG();

            System.IO.File.WriteAllBytes(dirName + fileName, bytes);
            Debug.Log("Cubemap file created " + dirName + fileName);
        }
        catch (System.Exception e)
        {
            Debug.LogError("Failed to save cubemap file since " + e.ToString());
			return false;
        }

		DestroyImmediate(tex);
		return true;
	}

}
